#![warn(
	clippy::all,
	clippy::pedantic,
	clippy::correctness,
	clippy::perf,
	clippy::style,
	clippy::suspicious,
	clippy::complexity,
	clippy::nursery,
	clippy::unwrap_used,
	unused_qualifications,
	rust_2018_idioms,
	trivial_casts,
	trivial_numeric_casts,
	unused_allocation,
	clippy::unnecessary_cast,
	clippy::cast_lossless,
	clippy::cast_possible_truncation,
	clippy::cast_possible_wrap,
	clippy::cast_precision_loss,
	clippy::cast_sign_loss,
	clippy::dbg_macro,
	clippy::deprecated_cfg_attr,
	clippy::separated_literal_suffix,
	deprecated
)]
#![forbid(deprecated_in_future)]
#![allow(clippy::missing_errors_doc, clippy::module_name_repetitions)]

use crate::{format_ctx::FFmpegFormatContext, utils::from_path};

use std::path::Path;

use ffmpeg_sys_next::{av_log_set_level, AV_LOG_FATAL};

mod codec_ctx;
mod dict;
mod error;
mod filter_graph;
mod format_ctx;
mod frame_decoder;
pub mod model;
mod thumbnailer;
mod utils;
mod video_frame;

pub use error::Error;
pub use frame_decoder::{FrameDecoder, ThumbnailSize, VideoFrame};
pub use model::FFmpegMediaData;
pub use thumbnailer::ThumbnailerBuilder;
use tokio::task::spawn_blocking;

/// Helper function to generate retrieve media data from from a video/audio file
pub async fn probe(filename: impl AsRef<Path> + Send) -> Result<FFmpegMediaData, Error> {
	// Reduce the amount of logs generated by FFmpeg
	unsafe { av_log_set_level(AV_LOG_FATAL) };

	// Dictionary to store format options
	// let mut format_opts = FFmpegDict::new(None);
	// Some MPEGTS specific option (copied from ffprobe)
	// let scan_all_pmts = c"scan_all_pmts";
	// format_opts.set(scan_all_pmts, c"1")?;

	// Open an input stream, read the header and allocate the format context
	spawn_blocking({
		let filename = filename.as_ref().to_path_buf();
		move || {
			let mut fmt_ctx = FFmpegFormatContext::open_file(from_path(filename)?.as_c_str())?;

			// // Reset MPEGTS specific option
			// format_opts.remove(scan_all_pmts)?;

			// Read packets of media file to get stream information.
			fmt_ctx.find_stream_info()?;

			Ok((&fmt_ctx).into())
		}
	})
	.await?
}

/// Helper function to generate a thumbnail file from a video file with reasonable defaults
pub async fn to_thumbnail(
	video_file_path: impl AsRef<Path> + Send,
	output_thumbnail_path: impl AsRef<Path> + Send,
	size: ThumbnailSize,
	quality: f32,
) -> Result<(), Error> {
	// Reduce the amount of logs generated by FFmpeg
	unsafe { av_log_set_level(AV_LOG_FATAL) };

	ThumbnailerBuilder::new()
		.size(size)
		.quality(quality)?
		.build()
		.process(video_file_path, output_thumbnail_path)
		.await
}

#[cfg(test)]
mod tests {
	use super::*;
	use tempfile::tempdir;
	use tokio::fs;

	#[tokio::test]
	#[ignore]
	async fn test_all_files() {
		let video_file_path = [
			Path::new("./samples/video_01.mp4"),
			Path::new("./samples/video_02.mov"),
			Path::new("./samples/video_03.mov"),
			Path::new("./samples/video_04.mov"),
			Path::new("./samples/video_05.mov"),
			Path::new("./samples/video_06.mov"),
			Path::new("./samples/video_07.mp4"),
			Path::new("./samples/video_08.mov"),
			Path::new("./samples/video_09.MP4"),
		];

		let expected_webp_files = [
			Path::new("./samples/video_01.webp"),
			Path::new("./samples/video_02.webp"),
			Path::new("./samples/video_03.webp"),
			Path::new("./samples/video_04.webp"),
			Path::new("./samples/video_05.webp"),
			Path::new("./samples/video_06.webp"),
			Path::new("./samples/video_07.webp"),
			Path::new("./samples/video_08.webp"),
			Path::new("./samples/video_09.webp"),
		];

		let root = tempdir().unwrap();
		let actual_webp_files = [
			root.path().join("video_01.webp"),
			root.path().join("video_02.webp"),
			root.path().join("video_03.webp"),
			root.path().join("video_04.webp"),
			root.path().join("video_05.webp"),
			root.path().join("video_06.webp"),
			root.path().join("video_07.webp"),
			root.path().join("video_08.webp"),
			root.path().join("video_09.webp"),
		];

		for (input, output) in video_file_path.iter().zip(actual_webp_files.iter()) {
			if let Err(e) = to_thumbnail(input, output, ThumbnailSize::Scale(128), 100.0).await {
				eprintln!("Error: {e}; Input: {}", input.display());
				panic!("{}", e);
			}
		}

		for (expected, actual) in expected_webp_files.iter().zip(actual_webp_files.iter()) {
			let expected_bytes = fs::read(expected).await.unwrap();
			let actual_bytes = fs::read(actual).await.unwrap();
			assert_eq!(expected_bytes, actual_bytes);
		}
	}
}
